package com.zero.zerocat;

import com.zero.servlet.ZeroServlet;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * ZeroCat的功能实现
 */
public class ZeroCatServer {
    // servlet名称对应servlet实例的map
    private Map<String, ZeroServlet> nameToServletMap = new ConcurrentHashMap<>();
    // servlet名称对应servlet全限定类名的map
    private Map<String, String> nameToClassNameMap = new HashMap<>();

    // 存放ZeroServlet的位置
    private String basePackage;

    public ZeroCatServer(String basePackage){
        this.basePackage= basePackage;
    }

    // ZeroCatServer启动方法
    public void start() throws Exception {
        // 加载指定包中所有servlet的全限定类名
        cacheClassName(basePackage);
        // 启动server服务
        runServer();
    }

    /**
     * 从com.zero.webapp中加载所有servlet的全限定类名
     *
     * @param basePackage
     */
    private void cacheClassName(String basePackage) {
        // 通过basePackage获取到对应的URL，basePackage中的.需要替换为/
        URL resource = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        if (resource == null) {
            return;
        }
        // 将resource转化为文件
        File file = new File(resource.getFile());
        // 遍历file下的所有class文件，并将其放在nameToClassNameMap中
        for (File f : file.listFiles()) {
            if (f.isDirectory()) {
                cacheClassName(basePackage + "." + f.getName());
            } else if(f.getName().endsWith(".class")) {
                // 将类的全限定类名放在map中
                String simpleClassName = f.getName().replace(".class", "");
                nameToClassNameMap.put(simpleClassName, basePackage + "." + simpleClassName);
            }
        }
    }

    /**
     * 以netty服务器的身份进行
     */
    private void runServer() {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);
        try {
            ServerBootstrap sb = new ServerBootstrap();
            // 配置事件组、通道类型、请求队列长度、长连接开启、childHandler处理过程
            sb.group(bossGroup,workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 100)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .handler(new LoggingHandler(LogLevel.DEBUG))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline()
                            .addLast(new HttpServerCodec()) // 配置HttpServer的编解码器
                            .addLast(new ZeroCatHandler(nameToClassNameMap, nameToServletMap));
                    }
                });
            int port = initPort();
            // 绑定端口并同步监听客户端连接
            ChannelFuture channelFuture = sb.bind(port).sync();
            System.out.println("ZeroCatServer 启动成功, port=" + port);
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException | DocumentException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private int initPort() throws DocumentException {
        // 获取server.xml文件的输入流
        InputStream in = ZeroCatServer.class.getClassLoader().getResourceAsStream("server.xml");
        // 从server.xml中获取端口号
        Element element = (Element)new SAXReader().read(in).selectSingleNode("//port");
        return Integer.valueOf(element.getText());
    }


}
